/**
 * Exam No: Y8467136
 */
package japrc2011;

import japrc2011.MapEntity.MapEntityType;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.TreeMap;
import javax.swing.Timer;


public final class RobotSimulator implements SimulatorInterface 
{
    private TreeMap<String, Robot> robots = new TreeMap<String, Robot>();
    private static ArrayList<GridLocation> walls = new ArrayList<GridLocation>(); //ArrayLists to save the added walls(all the walls)
    private SimGUIInterface gui;
    static int timecounter = 0; 
    MapTableModel newMap = new MapTableModel(); 
    Timer t,t2; 
    Thread t1,t5;
    static String current_time = ""; 
    boolean b = true; 
    boolean thread = false, thread5 = false; 
    Robot r; 
    
    
    private void notifyGUI()
    {
        if(gui != null)
        {
            gui.notifySimHasChanged();
        }
    }
    
    //overrides the methods of the SimulatorInterface(API for the simulation)
    @Override
    public ArrayList<Robot> getRobots()
    {
        return new ArrayList<Robot>(robots.values());
    }

    @Override
    public void addRobot(RobotInterface robot)
    {        
        //this simulator only supports one concrete implementation of robot
        try
        {
            r = (Robot) robot;
            //r.setLocation(new GridLocation(1,1));
        }
        catch (ClassCastException e)
        {
            throw new RobotSimulatorException("Unsupported robot implementation passed as parameter to addRobot()");
        }
        //Make the addRobot() method to throw a SimulationException if client code attempts to add two robots with the same name 
        if(robots.containsKey(r.getName())) //if the treeMap already contains the name of the robot then the program throws an exception.
        { 
        	throw new RobotSimulatorException("Duplicate robot! " + r.getName() + " already exists."); 
        } 
        else 
        { 
        	robots.put(r.getName(), r); //Associates the specified value with the specified key in this map. If the map previously contained a mapping for this key, the old value is replaced. 
        	notifyGUI();
        } 
    
    }
    
    /**
     * Sends commands to the robot
     */
    @Override
    public void sendCommand(String robotName, String commandString)
    {
        r = robots.get(robotName);//returns the value of the TreeMap with key=robotName. The value is an object of the Robot class.
        
        if(r == null)// if the Robot does not exist in the TreeMap, then the program throws an exception.
        {
            throw new RobotSimulatorException("Command sent to unknown robot: " + robotName);
        }
        
        String formated_cmd = r.checkFormatOfString(commandString);

        if(formated_cmd.equals("STOP"))//if the command is stop
        {
        	stopW_R();//call the stopW_R to stop any active WAIT or REPEAT
        }
        else
        {
        	//System.out.println("The command from the button pressed is:" + formated_cmd); 
        	r.receiveMap(getMapState()); //this is used to pass the entire map in the class robot, in order the robot to know if there are obstacles in the map and where.
        	r.receiveCommand(formated_cmd); //calls the receiveCommand method of the Robot class.
                
        	if(r.repeat_value == 1)//if the command that the robot received is repeat
        	{
        		t1 = new Thread(){//start a thread which 
            	public void run()
    			{
            		thread = true;//specifies if the thread is running or not. This value is used to the stopW_R() method
            		b = true;
            		while(b)
            		{
            			try 
            			{
            				sleep(r.thetime);//sleep for the specify time 
            				r.receiveMap(getMapState());//inform the robot for the current map state
            				r.receiveCommand(r.thecommand);//and then repeat
            				tick();//tick to execute the command.
            			} 
            			catch (InterruptedException e) 
            			{
    					
            			}
            		}
            		thread = false;//set to false when the thread is finished
    			}
    		};
    		t1.start();
        }
        notifyGUI();
        }
    }

    /**
     * Returns info for the robot
     */
    @Override
    public long getRobotInfo(String robotName, String infoName)
    {
        r = robots.get(robotName);
        
        if(r == null)
        {
            throw new RobotSimulatorException("Info requested from unknown robot: " + robotName);
        }
        
        return r.getInfo(infoName);// returns the commandCount(how many commands have been executed till now).
    }

    /**
     * Returns the specified robot
     */
    @Override
    public Robot getRobot(String name)
    {
        r = robots.get(name);
        if(r != null)
        {
            return r;
        }
        else
        {
            throw new RobotSimulatorException("Unknown robot: " + name);
        }
    }


    /**
     * Gets the current state of the simulated world, including all robots and obstacles therein
     * 
     */
    @Override
    public MapEntity[][] getMapState()//returns a 2D array of MapEntity objects
    {
        MapEntity [] [] mapView = new MapEntity[newMap.getColumnCount()][newMap.getRowCount()];
        
        for(Robot r: robots.values())//This code iterates over all the robots. Reads a Robot from the robots TreeMap and put this in the variable r.
        {
            GridLocation g = r.getLocation();//finds the location of the robot in the map
           	mapView[g.getX()][g.getY()] = new MapEntity(MapEntityType.ROBOT, r.getName());
        }
        
        for(int i=0; i < walls.size(); i++) //read all the elements(walls) in the array list one by one
        { //--
        	int wall_x = walls.get(i).getX(); //read the element(the element is a GridLocation element) and read the x value of the GridLocation element
        	int wall_y = walls.get(i).getY(); //read the element(the element is a GridLocation element) and read the y value of the GridLocation element
        	mapView[wall_x][wall_y] = new MapEntity(MapEntityType.WALL, "wall"); 
        } 
        
        return mapView;//returns a 2D array of MapEntity objects with dimensions equal to the dimensions of the simulated world. Empty locations are represented by null array entries.
    }

    /**
     * 
     */
    @Override
    public void setGUI(SimGUIInterface gui)
    {
        this.gui = gui;
    }

    /**
     * adds a new wall to the map in the specified location 
     */
    @Override
    public void addWall(japrc2011.GridLocation gridLocation)
    {
    	GridLocation gridLoc; 
    	//check if the gridlocation is correct. If it is not throw an exception!
    	try 
    	{ 
    		gridLoc = gridLocation; 
    	} 
    	catch(ClassCastException e)
    	{ 
    		throw new RobotSimulatorException("Unsupported gridlocation implementation passed as parameter to addWall()"); 
    	}
    	
    	//exception is thrown when the user tries to put a wall in a location where another robot or wall is.
    	MapEntity[][] current_map = getMapState();//get the current state of the map
    	//process the gridLocation(parameter received) to get the dimensions
    	String var = String.valueOf(gridLoc);
    	String var2 = var.substring(1,var.length()-1);
    	String[] var3 = var2.split(",");
    	if(!((current_map[Integer.parseInt(var3[0])][Integer.parseInt(var3[1])])==null)) //if there is an obstacle in this location, throw an exception  
    	{ 
    		throw new RobotSimulatorException("The location is not empty! Try to put your obstacle in another location.");
    	}
    	else//else add the wall
    	{
    		walls.add(gridLoc); //adds in an ArrayList the new location of the new added wall
        	notifyGUI();
    	}
    }    

    @Override
    public japrc2011.GridLocation getDimensions()
    {
    	//return null;
        return r.getLocation();
    }

    /**
     * returns the current sim time
     */
    @Override
    public int getSimTime()
    {
        return timecounter; 
    }

    @Override
    public void moveRobotTo(String robotName, japrc2011.GridLocation newLoc)
    {
    	
    }

    /**
     * Method used to set the size of the map
     */
    @Override
    public void setMapSize(int x, int y)
    {
    	int mapX2 = x; 
    	int mapY2 = y; 
    	newMap = new MapTableModel();  
      	newMap.setMapx(mapX2); //pass the new dimensions using the setter of the MapTableModel class
      	newMap.setMapy(mapY2); //pass the new dimensions using the setter of the MapTableModel class
      	//System.out.println("setMapSize: " + newMap.getRowCount() + newMap.getColumnCount()); 
    }

    /**
     * 1)Increases the sim time,
     * 2)calls the tick() method of the Robot
     * 3)calls the notifyGUI() method to refresh the GUI
     */
    @Override
    public void tick()
    {
    	timecounter++; // putting this hear when the robot waits and we send commands the simulation is increased while the robot waits.

    	if(r.wait_is_pressed == true) 
    	{
    		t5 = new Thread() {
    			public void run()
    		{
            		thread5 = true;//specifies if the thread is running or not
    				try {
						sleep(r.wait_interval);
					} 
    				catch (InterruptedException e) 
    				{
						
					}
    		    	//System.out.println(getSimTime() + " " + "secs"); 
    		    		
    		    	r.tick();
    		    	notifyGUI(); 
    		    	r.wait_is_pressed = false;
    		    	thread5 = false;
    		    	//System.out.println("Thread 5 stopped");
    		}
    		};
    		t5.start();
    	}
    	else
    	{
    	//System.out.println(getSimTime() + " " + "secs"); 
    		
    	r.tick();
    	notifyGUI(); 
    	}
    	
    }

    /**
     * starts a timer which calls the tick() method every real-world second
     */
    @Override
    public void start()
    {
    	t = new Timer(1000, new buttonActionListener()); //fires an action event once per second (as specified by the first argument to the Timer constructor). The second argument to the Timer constructor specifies a listener to receive the timer's action events.
    	t.setInitialDelay(1000); //adds a delay! The first tick is approximately one second after start() is called 
    	t.start(); //start the timer
    }

    /**
     * 
     */
    @Override
    public void stop()
    {
    	t.stop();//stop the timer.
    }
    
    
    /**
     * Method called when the command "STOP" is received
     * Cancels any active wait or repeat by a robot
     */
    public void stopW_R()
    {
    	if(thread == true)//cancel any active repeat
    	{
    		b = false;//stops the while loop
    		t1.interrupt();
    	}
    	//r.stopW_R();
    	if(thread5 == true)//cancel any active wait
    	{
    		t5.interrupt();
    	}
    }
    
    /**
     * a listener to receive the timer's action events
     * calls the tick() method
     */
    private class buttonActionListener implements ActionListener {
    	public void actionPerformed(ActionEvent event)
    	{
    		tick();
    		//timecounter = timecounter++;
    		/*Calendar cal = Calendar.getInstance();
    		SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss");
    		current_time = format.format(cal.getTime());
    		System.out.println(current_time);*/
    	}
    }
    
/*    public String getReal_time()
    {
    	return current_time;
    }*/

}
